# encoding: UTF-8

import csv
from collections import OrderedDict

from PyQt5 import QtCore, QtGui, QtWidgets

from base.event_engine import Event
from base.event_type import *
from const_struct.ctp_data import *
from tools.function import *


def loadFont():
    """载入界面字体设置"""
    try:
        f = open("window_setting.json")
        setting = json.load(f)
        family = setting['font']['fontFamily']
        size = setting['font']['fontSize']
        font = QtGui.QFont(family, size)
    except:
        font = QtGui.QFont(u'微软雅黑', 12)
    return font


BASIC_FONT = loadFont()


class StatusComboBox(QtWidgets.QComboBox):
    """状态栏的 ComboBox 单元格"""

    def __init__(self, name, label=None, data_event_engine=None, event_type=None, data_member=None, visible=True):
        """

        :param name: 控件名称
        :param label: 显示固定部分
        :param data_event_engine: 引起显示可变部分事件引擎，如果为None,仅显示固定部分
        :event_type: 事件类型
        """
        super(StatusComboBox, self).__init__()
        self.name = name
        self.setObjectName(name)
        self.label = label if label else ''
        self.data_event_engine = data_event_engine
        self.event_type = event_type
        self.data_member = data_member
        self.setVisible(visible)
        self.register()
        self.setEditable(False)
        # self.resize(100, 30)
        # print('StatusComboBox height:', self.height())

    def register(self):
        if self.data_event_engine and self.event_type:
            self.data_event_engine.register(self.event_type, self.update_event)

    def update_event(self, event):
        data = event.dict_['data']
        if self.data_member:
            self.addItem(':'.join([self.label, data.__dict__[self.data_member]]))
        else:
            self.addItem(':'.join([self.label, data]))


class StatusList(QtWidgets.QListWidget):
    """状态栏的list单元格"""

    def __init__(self, name, label=None, data_event_engine=None, event_type=None, data_member=None, visible=True):
        """

        :param name: 控件名称
        :param label: 显示固定部分
        :param data_event_engine: 引起显示可变部分事件引擎，如果为None,仅显示固定部分
        :event_type: 事件类型
        """
        super(StatusList, self).__init__()
        self.name = name
        self.setObjectName(name)
        self.label = label if label else ''
        self.data_event_engine = data_event_engine
        self.event_type = event_type
        self.data_member = data_member
        self.setVisible(visible)
        self.register()
        self.resize(100, 30)
        self.setEnabled(False)
        print('StatusList height:', self.height())

    def register(self):
        if self.data_event_engine and self.event_type:
            self.data_event_engine.register(self.event_type, self.update_event)

    def update_event(self, event):
        data = event.dict_['data']
        if self.data_member:
            self.addItem(':'.join([self.label, data.__dict__[self.data_member]]))
        else:
            self.addItem(':'.join([self.label, data]))


class StatusCell(QtWidgets.QLineEdit):
    """状态栏的单元格"""

    def __init__(self, name, label=None, data_event_engine=None, event_type=None, data_member=None, visible=True):
        """

        :param name: 控件名称
        :param label: 显示固定部分
        :param data_event_engine: 引起显示可变部分事件引擎，如果为None,仅显示固定部分
        :event_type: 事件类型
        """
        super(StatusCell, self).__init__()
        self.name = name
        self.setObjectName(name)
        self.label = label if label else ''
        self.data_event_engine = data_event_engine
        self.event_type = event_type
        self.data_member = data_member
        self.register()
        self.setVisible(visible)
        if self.label:
            self.setText(self.label)

        self.setEnabled(False)

    def register(self):
        if self.data_event_engine and self.event_type:
            self.data_event_engine.register(self.event_type, self.update_event)

    def update_event(self, event):
        data = event.dict_['data']
        if not (isinstance(data, dict) or isinstance(data, (bool, int, float, str))):
            data = data.__dict__
        if isinstance(data, bool):
            if data:
                data = '1'
            else:
                data = '0'
        if self.data_member:
            self.setText(''.join([self.label, str(safe_unicode(data[self.data_member]))]))
        else:
            self.setText(''.join([self.label, str(safe_unicode(data))]))


class BasicCell(QtWidgets.QTableWidgetItem):
    """基础的单元格"""

    def __init__(self, text=None, data_event_engine=None):
        """Constructor"""
        super(BasicCell, self).__init__()
        self.data = None
        self.data_event_engine = data_event_engine
        if text:
            self.setContent(text)

    def setContent(self, text):
        """设置内容"""
        if text == '0' or text == '0.0':
            self.setText('')
        else:
            self.setText(str(text))


class CheckCell(QtWidgets.QTableWidgetItem):
    """check单元格"""

    def __init__(self, text=None, data_event_engine=None):
        """Constructor"""
        super(CheckCell, self).__init__()
        self.data = None
        self.data_event_engine = data_event_engine
        self.setCheckState(QtCore.Qt.Unchecked)
        if text:
            self.setContent(text)

    def setContent(self, text):
        """设置内容"""
        if text == '0' or text == '0.0':
            self.setText('')
        else:
            self.setText(str(text))


class ComboBoxCell(QtWidgets.QComboBox):
    '''可设置为QTableWidgetItem的下拉框'''

    def __init__(self, items):
        super(ComboBoxCell, self).__init__()
        self.items = items

        if items:
            self.setContent(items)

    def setContent(self, items):
        for item in items:
            self.addItem(item)


class SpinCell(QtWidgets.QSpinBox):
    """可设置为QTableWidgetItem的QSpinBox控件"""

    def __init__(self, text=None, mainEngine=None):
        """Constructor"""
        super(SpinCell, self).__init__()
        # self.data = None
        # self.setRange(0,1000)
        # if text:
        #     self.setContent(text)

    def setContent(self, text):
        """设置内容"""

        if text == '0' or text == '0.0':
            self.setValue(0)
        else:
            self.setValue(int(text))
        # print(int(text))


########################################################################
class DirectionCell(QtWidgets.QTableWidgetItem):
    """用来显示买卖方向的单元格"""

    def __init__(self, text=None, mainEngine=None):
        """Constructor"""
        super(DirectionCell, self).__init__()
        self.data = None
        if text:
            self.setContent(text)
            # print(text)

    def setContent(self, text):
        """设置内容"""
        if text == DIRECTION_LONG or text == DIRECTION_NET:
            self.setForeground(QtWidgets.QColor('red'))
        elif text == DIRECTION_SHORT:
            self.setForeground(QtWidgets.QColor('green'))
        self.setText(text)


########################################################################
class NameCell(QtWidgets.QTableWidgetItem):
    """用来显示合约中文的单元格"""

    def __init__(self, text=None, data_event_engine=None):
        """Constructor"""
        super(NameCell, self).__init__()

        self.data_event_engine = data_event_engine
        self.data = None

        if text:
            self.setContent(text)

    def setContent(self, text):
        """设置内容"""
        # print(text)
        if self.data_event_engine and text:
            # 首先尝试正常获取合约对象
            contract = self.data_event_engine.getContract(text)

            # 如果能读取合约信息
            if contract:
                if isinstance(contract, ContractData):
                    self.setText(contract.name)
                if isinstance(contract, dict):
                    self.setText(contract['name'])


########################################################################
class BidCell(QtWidgets.QTableWidgetItem):
    """买价单元格"""

    def __init__(self, text=None, mainEngine=None):
        """Constructor"""
        super(BidCell, self).__init__()
        self.data = None

        self.setForeground(QtGui.QColor('black'))
        self.setBackground(QtGui.QColor(255, 174, 201))

        if text:
            self.setContent(text)

    def setContent(self, text):
        """设置内容"""
        self.setText(str(text))


########################################################################
class AskCell(QtWidgets.QTableWidgetItem):
    """买价单元格"""

    def __init__(self, text=None, mainEngine=None):
        """Constructor"""
        super(AskCell, self).__init__()
        self.data = None

        self.setForeground(QtGui.QColor('black'))
        self.setBackground(QtGui.QColor(160, 255, 160))

        if text:
            self.setContent(text)

    def setContent(self, text):
        """设置内容"""
        self.setText(str(text))


class BasicMonitor(QtWidgets.QTableWidget):
    """
    基础监控
    
    headerDict中的值对应的字典格式如下
    {'chinese': '中文名', 'cellType': BasicCell}
    
    """
    signal = QtCore.pyqtSignal(type(Event()))

    def __init__(self, mainEngine=None, eventEngine=None, parent=None, quoteEventEngine=None):
        """Constructor"""
        super(BasicMonitor, self).__init__(parent)

        self.mainEngine = mainEngine
        self.eventEngine = eventEngine
        self.quoteEventEngine = quoteEventEngine

        # 保存表头标签用
        self.headerDict = OrderedDict()  # 有序字典，key是英文名，value是对应的配置字典
        self.headerList = []  # 对应self.headerDict.keys()

        # 保存相关数据用
        self.dataDict = {}  # 字典，key是字段对应的数据，value是保存相关单元格的字典
        self.dataKey = ''  # 字典键对应的数据字段

        # 监控的事件类型
        self.eventType = ''

        # 字体
        self.font = None

        # 保存数据对象到单元格
        self.saveData = False

        # 默认不允许根据表头进行排序，需要的组件可以开启
        self.sorting = False

        # 初始化右键菜单
        self.menu = None
        self.initMenu()

    def setHeaderDict(self, headerDict):
        """设置表头有序字典"""
        self.headerDict = headerDict
        self.headerList = headerDict.keys()

    def setDataKey(self, dataKey):
        """设置数据字典的键"""
        self.dataKey = dataKey

    def setEventType(self, eventType):
        """设置监控的事件类型"""
        self.eventType = eventType

    def setFont(self, font):
        """设置字体"""
        self.font = font

    def setSaveData(self, saveData):
        """设置是否要保存数据到单元格"""
        self.saveData = saveData

    def initTable(self):
        """初始化表格"""
        # 设置表格的列数
        col = len(self.headerDict)
        self.setColumnCount(col)

        # 设置列表头
        labels = [d['chinese'] for d in self.headerDict.values()]
        self.setHorizontalHeaderLabels(labels)

        # 关闭左边的垂直表头
        self.verticalHeader().setVisible(False)

        # 设为不可编辑
        self.setEditTriggers(self.NoEditTriggers)

        # 设为行交替颜色
        self.setAlternatingRowColors(True)

        # 设置允许排序
        self.setSortingEnabled(self.sorting)

    def registerEvent(self):
        """注册GUI更新相关的事件监听"""
        # print('注册GUI更新相关的事件监听')
        self.signal.connect(self.updateEvent)
        self.eventEngine.register(self.eventType, self.signal.emit)
        if self.quoteEventEngine:
            self.quoteEventEngine.register(self.eventType, self.signal.emit)

    def updateEvent(self, event):
        """收到事件更新"""
        data = event.dict_['data']
        # print(data)

        self.updateData(data)

    def updateData(self, data):
        """将数据更新到表格中"""
        # 如果允许了排序功能，则插入数据前必须关闭，否则插入新的数据会变乱
        if self.sorting:
            self.setSortingEnabled(False)

        # 如果设置了dataKey，则采用存量更新模式
        if self.dataKey:
            key = data.__getattribute__(self.dataKey)
            # 如果键在数据字典中不存在，则先插入新的一行，并创建对应单元格
            if key not in self.dataDict:
                self.insertRow(0)
                d = {}
                for n, header in enumerate(self.headerList):
                    content = safe_unicode(data.__getattribute__(header))
                    cellType = self.headerDict[header]['cellType']
                    cell = cellType(content, self.mainEngine)

                    if self.font:
                        cell.setFont(self.font)  # 如果设置了特殊字体，则进行单元格设置

                    if self.saveData:  # 如果设置了保存数据对象，则进行对象保存
                        cell.data = data

                    # self.setItem(0, n, cell)
                    # print(self.headerDict[header]['cellType'] is BasicCell)
                    if self.headerDict[header]['cellType'] is BasicCell \
                            or self.headerDict[header]['cellType'] is NameCell \
                            or self.headerDict[header]['cellType'] is BidCell \
                            or self.headerDict[header]['cellType'] is AskCell \
                            or self.headerDict[header]['cellType'] is DirectionCell:
                        self.setItem(0, n, cell)
                    else:
                        # print(self.headerDict[header]['cellType'])
                        self.setCellWidget(0, n, cell)

                    d[header] = cell
                self.dataDict[key] = d
            # 否则如果已经存在，则直接更新相关单元格
            else:
                d = self.dataDict[key]
                for header in self.headerList:
                    content = safe_unicode(data.__getattribute__(header))
                    cell = d[header]
                    cell.setContent(content)

                    if self.saveData:  # 如果设置了保存数据对象，则进行对象保存
                        cell.data = data
                        # 否则采用增量更新模式
        else:
            self.insertRow(0)

            for n, header in enumerate(self.headerList):

                content = safe_unicode(data.__getattribute__(header))

                cellType = self.headerDict[header]['cellType']
                cell = cellType(content, self.mainEngine)  # , self.mainEngine
                # print((n, header, content,cell.text()))

                if self.font:
                    cell.setFont(self.font)

                if self.saveData:
                    cell.data = data

                self.setItem(0, n, cell)

                # 调整列宽
        self.resizeColumns()

        # 重新打开排序
        if self.sorting:
            self.setSortingEnabled(True)

    def resizeColumns(self):
        """调整各列的大小"""
        self.horizontalHeader().resizeSections(QtWidgets.QHeaderView.ResizeToContents)

    def setSorting(self, sorting):
        """设置是否允许根据表头排序"""
        self.sorting = sorting

    def saveToCsv(self):
        """保存表格内容到CSV文件"""
        # 先隐藏右键菜单
        self.menu.close()

        # 获取想要保存的文件名
        path = QtWidgets.QFileDialog.getSaveFileName(self, '保存数据', '', 'CSV(*.csv)')

        try:
            if path:
                with open(path, 'wb') as f:
                    writer = csv.writer(f)

                    # 保存标签
                    headers = [header.encode('gbk') for header in self.headerList]
                    writer.writerow(headers)

                    # 保存每行内容
                    for row in range(self.rowCount()):
                        rowdata = []
                        for column in range(self.columnCount()):
                            item = self.item(row, column)
                            if item is not None:
                                rowdata.append(
                                    item.text().encode('gbk'))
                            else:
                                rowdata.append('')
                        writer.writerow(rowdata)
        except IOError:
            pass

    def initMenu(self):
        """初始化右键菜单"""
        self.menu = QtWidgets.QMenu(self)

        saveAction = QtWidgets.QAction('保存内容', self)
        saveAction.triggered.connect(self.saveToCsv)

        self.menu.addAction(saveAction)

    def contextMenuEvent(self, event):
        """右键点击事件"""
        self.menu.popup(QtGui.QCursor.pos())


class QuoteMonitor(QtWidgets.QTableWidget):
    """市场监控组件"""
    signal = QtCore.pyqtSignal(type(Event()))

    def __init__(self, quoteEngine, eventQuoteEngine, parent=None, symbolSet=None):
        """Constructor"""
        super(QuoteMonitor, self).__init__(parent)
        self.mainEngine = quoteEngine
        self.eventEngine = eventQuoteEngine

        # 保存表头标签用
        self.headerDict = OrderedDict()  # 有序字典，key是英文名，value是对应的配置字典
        self.headerList = []  # 对应self.headerDict.keys()
        self.displaySymbolSet = set()
        if symbolSet: self.displaySymbolSet = symbolSet
        # print(self.displaySymbolSet)

        # 保存相关数据用
        self.dataDict = {}  # 字典，key是字段对应的数据，value是保存相关单元格的字典
        self.dataKey = ''  # 字典键对应的数据字段

        # 监控的事件类型
        self.eventType = ''

        # 字体
        self.font = None

        # 保存数据对象到单元格
        self.saveData = True

        # 默认不允许根据表头进行排序，需要的组件可以开启
        self.sorting = False

        # 初始化右键菜单
        self.menu = None
        self.initMenu()

        # 设置表头有序字典
        d = OrderedDict()
        d['symbol'] = {'chinese': u'合约代码', 'cellType': BasicCell}
        d['vtSymbol'] = {'chinese': u'名称', 'cellType': BasicCell}
        d['lastPrice'] = {'chinese': u'最新价', 'cellType': BasicCell}
        d['preClosePrice'] = {'chinese': u'昨收盘价', 'cellType': BasicCell}
        d['volume'] = {'chinese': u'成交量', 'cellType': BasicCell}
        d['openInterest'] = {'chinese': u'持仓量', 'cellType': BasicCell}
        d['openPrice'] = {'chinese': u'开盘价', 'cellType': BasicCell}
        d['highPrice'] = {'chinese': u'最高价', 'cellType': BasicCell}
        d['lowPrice'] = {'chinese': u'最低价', 'cellType': BasicCell}
        d['bidPrice1'] = {'chinese': u'买一价', 'cellType': BidCell}  # BidCell
        d['bidVolume1'] = {'chinese': u'买一量', 'cellType': BidCell}  # BidCell
        d['askPrice1'] = {'chinese': u'卖一价', 'cellType': AskCell}  # AskCell
        d['askVolume1'] = {'chinese': u'卖一量', 'cellType': AskCell}  # AskCell
        d['preClosePrice'] = {'chinese': '昨收', 'cellType': BasicCell}
        d['preSettlementPrice'] = {'chinese': '昨结', 'cellType': BasicCell}
        d['preOpenInterest'] = {'chinese': '昨持', 'cellType': BasicCell}
        d['time'] = {'chinese': u'时间', 'cellType': BasicCell}
        d['gatewayName'] = {'chinese': u'接口', 'cellType': BasicCell}

        self.setHeaderDict(d)

        # 设置数据键
        self.setDataKey('symbol')

        # 设置监控事件类型
        self.setEventType(EVENT_TICK)

        # 设置字体
        self.setFont(BASIC_FONT)

        # 设置允许排序
        self.setSorting(True)

        # 初始化表格
        self.initTable()

        # 注册事件监听
        self.registerEvent()

    def setHeaderDict(self, headerDict):
        """设置表头有序字典"""
        self.headerDict = headerDict
        self.headerList = headerDict.keys()

    def setDataKey(self, dataKey):
        """设置数据字典的键"""
        self.dataKey = dataKey

    def setEventType(self, eventType):
        """设置监控的事件类型"""
        self.eventType = eventType

    def setFont(self, font):
        """设置字体"""
        self.font = font

    def setSaveData(self, saveData):
        """设置是否要保存数据到单元格"""
        self.saveData = saveData

    def initTable(self):
        """初始化表格"""
        # 设置表格的列数
        col = len(self.headerDict)
        self.setColumnCount(col)

        # 设置列表头
        labels = [d['chinese'] for d in self.headerDict.values()]
        self.setHorizontalHeaderLabels(labels)

        # 关闭左边的垂直表头
        self.verticalHeader().setVisible(False)

        # 设为不可编辑
        self.setEditTriggers(self.NoEditTriggers)

        # 设为行交替颜色
        self.setAlternatingRowColors(True)

        # 设置允许排序
        self.setSortingEnabled(self.sorting)

    def registerEvent(self):
        """注册GUI更新相关的事件监听"""
        self.signal.connect(self.updateEvent)
        self.eventEngine.register(self.eventType, self.signal.emit)

    def updateEvent(self, event):
        """收到事件更新"""
        data = event.dict_['data']
        # print(data)
        self.updateData(data)

    def updateData(self, data):
        """将数据更新到表格中"""
        # 如果允许了排序功能，则插入数据前必须关闭，否则插入新的数据会变乱
        if self.sorting:
            self.setSortingEnabled(False)

        # 如果设置了dataKey，则采用存量更新模式
        if self.dataKey:
            # print(data.symbol)
            key = data.__getattribute__(self.dataKey)

            if not (key in self.displaySymbolSet):
                # print(key)
                return

            # 如果键在数据字典中不存在，则先插入新的一行，并创建对应单元格
            if key not in self.dataDict:
                self.insertRow(0)
                d = {}
                for n, header in enumerate(self.headerList):
                    content = safe_unicode(data.__getattribute__(header))
                    cellType = self.headerDict[header]['cellType']
                    cell = cellType(content, self.mainEngine)

                    if self.font:
                        cell.setFont(self.font)  # 如果设置了特殊字体，则进行单元格设置

                    if self.saveData:  # 如果设置了保存数据对象，则进行对象保存
                        cell.data = data

                    self.setItem(0, n, cell)
                    d[header] = cell
                self.dataDict[key] = d
            # 否则如果已经存在，则直接更新相关单元格
            else:
                d = self.dataDict[key]
                for header in self.headerList:
                    content = safe_unicode(data.__getattribute__(header))
                    cell = d[header]
                    cell.setContent(content)

                    if self.saveData:  # 如果设置了保存数据对象，则进行对象保存
                        cell.data = data
                        # 否则采用增量更新模式
        else:
            self.insertRow(0)
            for n, header in enumerate(self.headerList):
                content = safe_unicode(data.__getattribute__(header))
                cellType = self.headerDict[header]['cellType']
                cell = cellType(content, self.mainEngine)

                if self.font:
                    cell.setFont(self.font)

                if self.saveData:
                    cell.data = data

                self.setItem(0, n, cell)

                # 调整列宽
        self.resizeColumns()

        # 重新打开排序
        if self.sorting:
            self.setSortingEnabled(True)

    def resizeColumns(self):
        """调整各列的大小"""
        self.horizontalHeader().resizeSections(QtWidgets.QHeaderView.ResizeToContents)

    def setSorting(self, sorting):
        """设置是否允许根据表头排序"""
        self.sorting = sorting

    def saveToCsv(self):
        """保存表格内容到CSV文件"""
        # 先隐藏右键菜单
        self.menu.close()

        # 获取想要保存的文件名
        path = QtWidgets.QFileDialog.getSaveFileName(self, '保存数据', '', 'CSV(*.csv)')

        try:
            if not path.isEmpty():
                with open(path, 'wb') as f:
                    writer = csv.writer(f)

                    # 保存标签
                    headers = [header.encode('gbk') for header in self.headerList]
                    writer.writerow(headers)

                    # 保存每行内容
                    for row in range(self.rowCount()):
                        rowdata = []
                        for column in range(self.columnCount()):
                            item = self.item(row, column)
                            if item is not None:
                                rowdata.append(
                                    item.text().encode('gbk'))
                            else:
                                rowdata.append('')
                        writer.writerow(rowdata)
        except IOError:
            pass

    def initMenu(self):
        """初始化右键菜单"""
        self.menu = QtWidgets.QMenu(self)

        saveAction = QtWidgets.QAction('保存内容', self)
        saveAction.triggered.connect(self.saveToCsv)

        self.menu.addAction(saveAction)

    def contextMenuEvent(self, event):
        """右键点击事件"""
        self.menu.popup(QtGui.QCursor.pos())


class LogMonitor(BasicMonitor):
    """日志监控"""

    def __init__(self, mainEngine, eventEngine, quoteEventEngine=None, parent=None):
        """Constructor"""
        super(LogMonitor, self).__init__(mainEngine, eventEngine, parent)
        self.quoteEventEngine = quoteEventEngine
        d = OrderedDict()
        d['logTime'] = {'chinese': u'时间', 'cellType': BasicCell}
        d['logContent'] = {'chinese': u'内容', 'cellType': BasicCell}
        d['gatewayName'] = {'chinese': u'接口', 'cellType': BasicCell}
        self.setHeaderDict(d)

        self.setEventType(EVENT_LOG)
        self.setFont(BASIC_FONT)
        self.initTable()
        self.registerEvent()

    def registerEvent(self):
        # super(LogMonitor,self).registerEvent()
        BasicMonitor.registerEvent(self)


class MarketTickMonitor(QtWidgets.QTableWidget):
    """市场即时行情监控组件"""
    signal = QtCore.pyqtSignal(type(Event()))

    def __init__(self, data_event, parent=None, symbolSet=None):
        """Constructor"""
        super(MarketTickMonitor, self).__init__(parent=parent)
        self.data_event = data_event

        # 保存表头标签用
        self.headerDict = OrderedDict()  # 有序字典，key是英文名，value是对应的配置字典
        self.headerList = []  # 对应self.headerDict.keys()
        self.displaySymbolSet = set()
        if symbolSet:
            self.displaySymbolSet = symbolSet
        # print(self.displaySymbolSet)

        # 保存相关数据用
        self.dataDict = {}  # 字典，key是字段对应的数据，value是保存相关单元格的字典
        self.dataKey = 'symbol'  # 字典键对应的数据字段

        # 监控的事件类型
        self.eventType = ''

        # 字体
        self.font = None

        # 保存数据对象到单元格
        self.saveData = True

        # 默认不允许根据表头进行排序，需要的组件可以开启
        self.sorting = False

        # 设置表头有序字典
        d = OrderedDict()
        d['symbol'] = {'chinese': u'合约代码', 'cellType': BasicCell, 'display': 1}
        d['lastPrice'] = {'chinese': u'最新价', 'cellType': BasicCell, 'display': 1}
        d['preClosePrice'] = {'chinese': u'昨收盘价', 'cellType': BasicCell, 'display': 1}
        d['volume'] = {'chinese': u'成交量', 'cellType': BasicCell, 'display': 1}
        d['openInterest'] = {'chinese': u'持仓量', 'cellType': BasicCell, 'display': 1}
        d['openPrice'] = {'chinese': u'开盘价', 'cellType': BasicCell, 'display': 1}
        d['highPrice'] = {'chinese': u'最高价', 'cellType': BasicCell, 'display': 1}
        d['lowPrice'] = {'chinese': u'最低价', 'cellType': BasicCell, 'display': 1}
        d['bidPrice1'] = {'chinese': u'买一价', 'cellType': BidCell, 'display': 1}  # BidCell
        d['bidVolume1'] = {'chinese': u'买一量', 'cellType': BidCell, 'display': 1}  # BidCell
        d['askPrice1'] = {'chinese': u'卖一价', 'cellType': AskCell, 'display': 1}  # AskCell
        d['askVolume1'] = {'chinese': u'卖一量', 'cellType': AskCell, 'display': 1}  # AskCell
        d['preClosePrice'] = {'chinese': '昨收', 'cellType': BasicCell, 'display': 1}
        d['preSettlementPrice'] = {'chinese': '昨结', 'cellType': BasicCell, 'display': 1}
        d['preOpenInterest'] = {'chinese': '昨持', 'cellType': BasicCell, 'display': 1}
        d['time'] = {'chinese': u'时间', 'cellType': BasicCell, 'display': 1}
        # d['gatewayName'] = {'chinese': u'接口', 'cellType': BasicCell, 'display': 1}

        self.setHeaderDict(d)

        # 设置数据键
        self.setDataKey('symbol')

        # 设置监控事件类型
        self.setEventType(EVENT_TICK)

        # 设置字体
        self.setFont(BASIC_FONT)

        # 设置允许排序
        self.setSorting(True)

        # 初始化表格
        self.initTable()

        # 注册事件监听
        self.registerEvent()

        # 初始化右键菜单
        self.menu = None
        self.initMenu()

    def setHeaderDict(self, headerDict):
        """设置表头有序字典"""
        self.headerDict = headerDict
        self.headerList = headerDict.keys()

    def setDataKey(self, dataKey):
        """设置数据字典的键"""
        self.dataKey = dataKey

    def setEventType(self, eventType):
        """设置监控的事件类型"""
        self.eventType = eventType

    def setFont(self, font):
        """设置字体"""
        self.font = font

    def setSaveData(self, saveData):
        """设置是否要保存数据到单元格"""
        self.saveData = saveData

    def initTable(self):
        """初始化表格"""
        # 设置表格的列数
        col = len(self.headerDict)
        self.setColumnCount(col)

        # 设置列表头
        labels = [d['chinese'] for d in self.headerDict.values() if d['display'] == 1]
        self.setHorizontalHeaderLabels(labels)

        # 关闭左边的垂直表头
        self.verticalHeader().setVisible(False)

        # 设为不可编辑
        self.setEditTriggers(self.NoEditTriggers)

        # 设为行交替颜色
        self.setAlternatingRowColors(True)

        # 设置允许排序
        self.setSortingEnabled(self.sorting)

    def registerEvent(self):
        """注册GUI更新相关的事件监听"""
        self.signal.connect(self.updateEvent)
        self.data_event.register(self.eventType, self.signal.emit)

    def updateEvent(self, event):
        """收到事件更新"""
        data = event.dict_['data']
        # print(data)
        self.updateData(data)

    def updateData(self, data):
        """将数据更新到表格中"""
        # 如果允许了排序功能，则插入数据前必须关闭，否则插入新的数据会变乱
        if self.sorting:
            self.setSortingEnabled(False)

        # 如果设置了dataKey，则采用存量更新模式
        key = data.__getattribute__(self.dataKey)

        if not (key in self.displaySymbolSet):
            # print(key)
            return

        # 如果键在数据字典中不存在，则先插入新的一行，并创建对应单元格
        if key not in self.dataDict:
            self.insertRow(0)
            d = {}
            for n, header in enumerate(self.headerList):
                content = safe_unicode(data.__getattribute__(header))
                cellType = self.headerDict[header]['cellType']
                cell = cellType(content, self.data_event)

                if self.font:
                    cell.setFont(self.font)  # 如果设置了特殊字体，则进行单元格设置

                # if self.saveData:  # 如果设置了保存数据对象，则进行对象保存
                #     cell.data = data

                self.setItem(0, n, cell)
                d[header] = cell
            self.dataDict[key] = d
        # 否则如果已经存在，则直接更新相关单元格
        else:
            d = self.dataDict[key]
            for header in self.headerList:
                content = safe_unicode(data.__getattribute__(header))
                cell = d[header]
                cell.setContent(content)

                # if self.saveData:  # 如果设置了保存数据对象，则进行对象保存
                #     cell.data = data

        # 调整列宽
        # self.resizeColumns()

        # 重新打开排序
        if self.sorting:
            self.setSortingEnabled(True)

    def resizeColumns(self):
        """调整各列的大小"""
        self.horizontalHeader().resizeSections(QtWidgets.QHeaderView.ResizeToContents)

    def setSorting(self, sorting):
        """设置是否允许根据表头排序"""
        self.sorting = sorting

    def saveToCsv(self):
        """保存表格内容到CSV文件"""
        # 先隐藏右键菜单
        self.menu.close()

        # 获取想要保存的文件名
        path = QtWidgets.QFileDialog.getSaveFileName(self, '保存数据', '', 'CSV(*.csv)')

        try:
            if not path.isEmpty():
                with open(path, 'wb') as f:
                    writer = csv.writer(f)

                    # 保存标签
                    headers = [header.encode('gbk') for header in self.headerList]
                    writer.writerow(headers)

                    # 保存每行内容
                    for row in range(self.rowCount()):
                        rowdata = []
                        for column in range(self.columnCount()):
                            item = self.item(row, column)
                            if item is not None:
                                rowdata.append(
                                    item.text().encode('gbk'))
                            else:
                                rowdata.append('')
                        writer.writerow(rowdata)
        except IOError:
            pass

    def initMenu(self):
        """初始化右键菜单"""
        self.menu = QtWidgets.QMenu(self)
        selectDisplayItem = QtWidgets.QMenu('选择列', self)
        itms = {k: {'display': v['chinese'], 'checked': 1} for k, v in self.headerDict.items()}
        sdi = create_check_menu_list(self, itms)
        for act in sdi.values():
            selectDisplayItem.addAction(act)

        self.menu.addMenu(selectDisplayItem)

        saveAction = QtWidgets.QAction('保存内容', self)
        saveAction.triggered.connect(self.saveToCsv)

        self.menu.addAction(saveAction)

    def contextMenuEvent(self, event):
        """右键点击事件"""
        self.menu.popup(QtGui.QCursor.pos())


class ErrorMonitor(BasicMonitor):
    """错误监控"""

    def __init__(self, mainEngine, eventEngine, quoteEventEngine=None, parent=None):
        """Constructor"""
        super(ErrorMonitor, self).__init__(mainEngine, eventEngine, parent, quoteEventEngine=quoteEventEngine)
        self.quoteEventEngine = quoteEventEngine
        d = OrderedDict()
        d['errorTime'] = {'chinese': u'错误时间', 'cellType': BasicCell}
        d['errorID'] = {'chinese': u'错误代码', 'cellType': BasicCell}
        d['errorMsg'] = {'chinese': u'错误信息', 'cellType': BasicCell}
        d['additionalInfo'] = {'chinese': u'补充信息', 'cellType': BasicCell}
        d['gatewayName'] = {'chinese': u'接口', 'cellType': BasicCell}
        self.setHeaderDict(d)

        self.setEventType(EVENT_RTN_ERROR)
        self.setFont(BASIC_FONT)
        self.initTable()
        self.registerEvent()

    def registerEvent(self):
        # super(ErrorMonitor,self).registerEvent()
        BasicMonitor.registerEvent(self)


def create_check_menu_list(parent, items):
    """
    创建选择型菜单
    :param parent:
    :param items: {'name':{'display':'display','checked':1}}
    :return:
    """
    rtn = {}
    for n, v in items.items():
        rtn[n] = QtWidgets.QAction(v['display'], parent=parent)
        rtn[n].setObjectName(n)
        rtn[n].setCheckable(True)
        rtn[n].setChecked(v['checked'] == 1)
    return rtn


########################################################################
class TradeMonitor(BasicMonitor):
    """成交监控"""

    def __init__(self, mainEngine, eventEngine, parent=None):
        """Constructor"""
        super(TradeMonitor, self).__init__(mainEngine, eventEngine, parent)

        d = OrderedDict()
        d['TradeID'] = {'chinese': u'成交编号', 'cellType': BasicCell}
        d['orderID'] = {'chinese': u'委托编号', 'cellType': BasicCell}
        d['symbol'] = {'chinese': u'合约代码', 'cellType': BasicCell}
        d['vtSymbol'] = {'chinese': u'名称', 'cellType': NameCell}
        d['direction'] = {'chinese': u'方向', 'cellType': BasicCell}  # DirectionCell
        d['offset'] = {'chinese': u'开平', 'cellType': BasicCell}
        d['price'] = {'chinese': u'价格', 'cellType': BasicCell}
        d['volume'] = {'chinese': u'数量', 'cellType': BasicCell}
        d['TradeTime'] = {'chinese': u'成交时间', 'cellType': BasicCell}
        d['gatewayName'] = {'chinese': u'接口', 'cellType': BasicCell}

        self.setHeaderDict(d)
        self.setEventType(EVENT_RTN_TRADE)
        self.setFont(BASIC_FONT)
        self.initTable()
        self.registerEvent()


########################################################################
class OrderMonitor(BasicMonitor):
    """委托监控"""

    def __init__(self, mainEngine, eventEngine, parent=None):
        """Constructor"""
        super(OrderMonitor, self).__init__(mainEngine, eventEngine, parent)

        self.mainEngine = mainEngine
        self.saveData = True

        d = OrderedDict()
        d['orderID'] = {'chinese': u'委托编号', 'cellType': BasicCell}
        d['symbol'] = {'chinese': u'合约代码', 'cellType': BasicCell}
        d['vtSymbol'] = {'chinese': u'名称', 'cellType': NameCell}
        d['direction'] = {'chinese': u'方向', 'cellType': BasicCell}  # DirectionCell
        d['offset'] = {'chinese': u'开平', 'cellType': BasicCell}
        d['price'] = {'chinese': u'价格', 'cellType': BasicCell}
        d['totalVolume'] = {'chinese': u'委托数量', 'cellType': BasicCell}
        d['tradedVolume'] = {'chinese': u'成交数量', 'cellType': BasicCell}
        d['status'] = {'chinese': u'状态', 'cellType': BasicCell}
        d['orderTime'] = {'chinese': u'委托时间', 'cellType': BasicCell}
        d['cancelTime'] = {'chinese': u'撤销时间', 'cellType': BasicCell}
        d['frontID'] = {'chinese': u'前置编号', 'cellType': BasicCell}
        d['sessionID'] = {'chinese': u'会话编号', 'cellType': BasicCell}
        # d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
        self.setHeaderDict(d)

        self.setDataKey('orderID')
        self.setEventType(EVENT_RTN_ORDER)
        self.setFont(BASIC_FONT)
        # self.setSaveData(True)

        self.initTable()
        self.registerEvent()

        self.connectSignal()

    def connectSignal(self):
        """连接信号"""
        # 双击单元格撤单
        self.itemDoubleClicked.connect(self.cancelOrder)

    def cancelOrder(self, cell):
        """根据单元格的数据撤单"""
        order = cell.data
        print(order.__dict__)
        req = CancelOrderReq()
        req.symbol = order.symbol
        req.exchange = order.exchange
        req.frontID = order.frontID
        req.sessionID = order.sessionID
        req.orderID = order.orderID
        req.orderSysID = order.rawData['OrderSysID'].strip()
        self.mainEngine.delete_order(req, order.gatewayName)


class UTOrderMonitor(BasicMonitor):
    """未成交委托监控"""

    def __init__(self, mainEngine, eventEngine, parent=None):
        """Constructor"""

        super(UTOrderMonitor, self).__init__(mainEngine, eventEngine, parent)
        self.saveData = True

        self.mainEngine = mainEngine

        d = OrderedDict()
        d['orderID'] = {'chinese': u'委托编号', 'cellType': BasicCell}
        d['symbol'] = {'chinese': u'合约代码', 'cellType': BasicCell}
        d['vtSymbol'] = {'chinese': u'名称', 'cellType': NameCell}
        d['direction'] = {'chinese': u'方向', 'cellType': BasicCell}  # DirectionCell
        d['offset'] = {'chinese': u'开平', 'cellType': BasicCell}
        d['price'] = {'chinese': u'价格', 'cellType': BasicCell}
        d['totalVolume'] = {'chinese': u'委托数量', 'cellType': SpinCell}  # SpinCell
        d['tradedVolume'] = {'chinese': u'成交数量', 'cellType': BasicCell}
        d['status'] = {'chinese': u'状态', 'cellType': BasicCell}
        d['orderTime'] = {'chinese': u'委托时间', 'cellType': BasicCell}
        d['cancelTime'] = {'chinese': u'撤销时间', 'cellType': BasicCell}
        d['frontID'] = {'chinese': u'前置编号', 'cellType': BasicCell}
        d['sessionID'] = {'chinese': u'会话编号', 'cellType': BasicCell}
        # d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
        self.setHeaderDict(d)

        self.setDataKey('orderID')
        self.setEventType(EVENT_UNTRADE)
        self.setFont(BASIC_FONT)
        # self.setSaveData(True)

        self.initTable()
        self.registerEvent()

        self.connectSignal()

    def connectSignal(self):
        """连接信号"""
        # 双击单元格撤单
        self.itemDoubleClicked.connect(self.cancelOrder)

    def cancelOrder(self, data):
        """根据单元格的数据撤单"""
        # currow=self.currentRow()
        data = self.currentItem()  # .data
        # print(type(data),data)
        order = data.data
        # print('cancelOrder:', data.text(), order.__dict__)

        req = CancelOrderReq()
        req.symbol = order.symbol
        req.exchange = order.exchange
        req.frontID = order.frontID
        req.sessionID = order.sessionID
        req.orderID = order.orderID.strip()
        req.orderSysID = order.rawData['OrderSysID']
        # print(len(order.orderSysID),req.orderSysID)

        self.mainEngine.delete_order(req, order.gatewayName)

    def initMenu(self):
        """初始化右键菜单"""
        self.menu = QtWidgets.QMenu(self)
        saveAction = QtWidgets.QAction('保存内容', self)
        saveAction.triggered.connect(self.saveToCsv)
        self.menu.addAction(saveAction)
        cancelOrderAction = QtWidgets.QAction('取消委托', self)
        cancelOrderAction.triggered.connect(self.cancelOrder)
        self.menu.addAction(cancelOrderAction)


########################################################################
class PositionMonitor(BasicMonitor):
    """持仓监控"""

    def __init__(self, mainEngine, eventEngine, parent=None):
        """Constructor"""
        super(PositionMonitor, self).__init__(mainEngine, eventEngine, parent)

        d = OrderedDict()
        d['symbol'] = {'chinese': u'合约代码', 'cellType': BasicCell}
        d['vtSymbol'] = {'chinese': u'名称', 'cellType': BasicCell}  # NameCell
        d['direction'] = {'chinese': u'方向', 'cellType': BasicCell}  # DirectionCell
        d['position'] = {'chinese': u'持仓量', 'cellType': BasicCell}
        d['ydPosition'] = {'chinese': u'昨持仓', 'cellType': BasicCell}
        d['frozen'] = {'chinese': u'冻结量', 'cellType': BasicCell}
        d['price'] = {'chinese': u'价格', 'cellType': BasicCell}
        # d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
        self.setHeaderDict(d)

        self.setDataKey('vtPositionName')
        self.setEventType(EVENT_RSP_POSITION)
        self.setFont(BASIC_FONT)
        # self.setSaveData(True)

        self.initTable()
        self.registerEvent()


########################################################################
class AccountMonitor(BasicMonitor):
    """账户监控"""

    def __init__(self, mainEngine, eventEngine, parent=None):
        """Constructor"""
        super(AccountMonitor, self).__init__(mainEngine, eventEngine, parent)

        d = OrderedDict()
        d['AccountID'] = {'chinese': u'账户', 'cellType': BasicCell}
        d['PreBalance'] = {'chinese': u'昨结', 'cellType': BasicCell}
        d['Balance'] = {'chinese': u'净值', 'cellType': BasicCell}
        d['Available'] = {'chinese': u'可用', 'cellType': BasicCell}
        d['Commission'] = {'chinese': u'手续费', 'cellType': BasicCell}
        d['CurrMargin'] = {'chinese': u'保证金', 'cellType': BasicCell}
        d['CloseProfit'] = {'chinese': u'平仓盈亏', 'cellType': BasicCell}
        d['PositionProfit'] = {'chinese': u'持仓盈亏', 'cellType': BasicCell}
        d['CashIn'] = {'chinese': u'资金进出', 'cellType': BasicCell}
        # d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
        self.setHeaderDict(d)

        self.setDataKey('vtAccountID')
        self.setEventType(EVENT_RSP_ACCOUNT)
        self.setFont(BASIC_FONT)
        self.initTable()
        self.registerEvent()


########################################################################
class TradingWidget(QtWidgets.QFrame):
    """简单交易组件"""
    signal = QtCore.pyqtSignal(type(Event()))

    directionList = [DIRECTION_LONG,
                     DIRECTION_SHORT]

    offsetList = [OFFSET_OPEN,
                  OFFSET_CLOSE,
                  OFFSET_CLOSEYESTERDAY,
                  OFFSET_CLOSETODAY]

    priceTypeList = [PRICETYPE_LIMITPRICE,
                     PRICETYPE_MARKETPRICE,
                     PRICETYPE_FAK,
                     PRICETYPE_FOK]

    exchangeList = [EXCHANGE_NONE,
                    EXCHANGE_CFFEX,
                    EXCHANGE_SHFE,
                    EXCHANGE_DCE,
                    EXCHANGE_CZCE,
                    EXCHANGE_SSE,
                    EXCHANGE_SZSE,
                    EXCHANGE_SGE,
                    EXCHANGE_HKEX,
                    EXCHANGE_SMART,
                    EXCHANGE_NYMEX,
                    EXCHANGE_GLOBEX,
                    EXCHANGE_IDEALPRO]

    currencyList = [CURRENCY_NONE,
                    CURRENCY_CNY,
                    CURRENCY_USD]

    productClassList = [PRODUCT_NONE,
                        PRODUCT_EQUITY,
                        PRODUCT_FUTURES,
                        PRODUCT_OPTION,
                        PRODUCT_FOREX]

    gatewayList = ['']

    def __init__(self, mainEngine, eventEngine, parent=None, quoteEventEngine=None):
        """Constructor"""
        super(TradingWidget, self).__init__(parent)
        self.mainEngine = mainEngine
        self.eventEngine = eventEngine
        self.quoteEventEngine = quoteEventEngine

        self.symbol = ''

        # 添加交易接口
        self.gatewayList.extend(mainEngine.gatewayDict.keys())

        self.initUi()
        self.connectSignal()

    def initUi(self):
        """初始化界面"""
        self.setWindowTitle(u'交易')
        self.setMaximumWidth(400)
        self.setFrameShape(self.Box)  # 设置边框
        self.setLineWidth(1)

        # 左边部分
        labelSymbol = QtWidgets.QLabel(u'代码')
        labelName = QtWidgets.QLabel(u'名称')
        labelDirection = QtWidgets.QLabel(u'方向类型')
        labelOffset = QtWidgets.QLabel(u'开平')
        labelPrice = QtWidgets.QLabel(u'价格')
        labelVolume = QtWidgets.QLabel(u'数量')
        labelPriceType = QtWidgets.QLabel(u'价格类型')
        labelExchange = QtWidgets.QLabel(u'交易所')
        labelCurrency = QtWidgets.QLabel(u'货币')
        labelProductClass = QtWidgets.QLabel(u'产品类型')
        labelGateway = QtWidgets.QLabel(u'交易接口')

        self.lineSymbol = QtWidgets.QLineEdit()
        self.lineName = QtWidgets.QLineEdit()

        self.comboDirection = QtWidgets.QComboBox()
        self.comboDirection.addItems(self.directionList)

        self.comboOffset = QtWidgets.QComboBox()
        self.comboOffset.addItems(self.offsetList)

        self.spinPrice = QtWidgets.QDoubleSpinBox()
        self.spinPrice.setDecimals(4)
        self.spinPrice.setMinimum(0)
        self.spinPrice.setMaximum(100000)

        self.spinVolume = QtWidgets.QSpinBox()
        self.spinVolume.setMinimum(0)
        self.spinVolume.setMaximum(1000000)

        self.comboPriceType = QtWidgets.QComboBox()
        self.comboPriceType.addItems(self.priceTypeList)

        self.comboExchange = QtWidgets.QComboBox()
        self.comboExchange.addItems(self.exchangeList)

        self.comboCurrency = QtWidgets.QComboBox()
        self.comboCurrency.addItems(self.currencyList)

        self.comboProductClass = QtWidgets.QComboBox()
        self.comboProductClass.addItems(self.productClassList)

        self.comboGateway = QtWidgets.QComboBox()
        self.comboGateway.addItems(self.gatewayList)

        gridleft = QtWidgets.QGridLayout()
        gridleft.addWidget(labelSymbol, 0, 0)
        gridleft.addWidget(labelName, 1, 0)
        gridleft.addWidget(labelDirection, 2, 0)
        gridleft.addWidget(labelOffset, 3, 0)
        gridleft.addWidget(labelPrice, 4, 0)
        gridleft.addWidget(labelVolume, 5, 0)
        gridleft.addWidget(labelPriceType, 6, 0)
        gridleft.addWidget(labelExchange, 7, 0)
        gridleft.addWidget(labelCurrency, 8, 0)
        gridleft.addWidget(labelProductClass, 9, 0)
        gridleft.addWidget(labelGateway, 10, 0)

        gridleft.addWidget(self.lineSymbol, 0, 1)
        gridleft.addWidget(self.lineName, 1, 1)
        gridleft.addWidget(self.comboDirection, 2, 1)
        gridleft.addWidget(self.comboOffset, 3, 1)
        gridleft.addWidget(self.spinPrice, 4, 1)
        gridleft.addWidget(self.spinVolume, 5, 1)
        gridleft.addWidget(self.comboPriceType, 6, 1)
        gridleft.addWidget(self.comboExchange, 7, 1)
        gridleft.addWidget(self.comboCurrency, 8, 1)
        gridleft.addWidget(self.comboProductClass, 9, 1)
        gridleft.addWidget(self.comboGateway, 10, 1)

        # 右边部分
        labelBid1 = QtWidgets.QLabel(u'买一')
        labelBid2 = QtWidgets.QLabel(u'买二')
        labelBid3 = QtWidgets.QLabel(u'买三')
        labelBid4 = QtWidgets.QLabel(u'买四')
        labelBid5 = QtWidgets.QLabel(u'买五')

        labelAsk1 = QtWidgets.QLabel(u'卖一')
        labelAsk2 = QtWidgets.QLabel(u'卖二')
        labelAsk3 = QtWidgets.QLabel(u'卖三')
        labelAsk4 = QtWidgets.QLabel(u'卖四')
        labelAsk5 = QtWidgets.QLabel(u'卖五')

        self.labelBidPrice1 = QtWidgets.QLabel()
        self.labelBidPrice2 = QtWidgets.QLabel()
        self.labelBidPrice3 = QtWidgets.QLabel()
        self.labelBidPrice4 = QtWidgets.QLabel()
        self.labelBidPrice5 = QtWidgets.QLabel()
        self.labelBidVolume1 = QtWidgets.QLabel()
        self.labelBidVolume2 = QtWidgets.QLabel()
        self.labelBidVolume3 = QtWidgets.QLabel()
        self.labelBidVolume4 = QtWidgets.QLabel()
        self.labelBidVolume5 = QtWidgets.QLabel()

        self.labelAskPrice1 = QtWidgets.QLabel()
        self.labelAskPrice2 = QtWidgets.QLabel()
        self.labelAskPrice3 = QtWidgets.QLabel()
        self.labelAskPrice4 = QtWidgets.QLabel()
        self.labelAskPrice5 = QtWidgets.QLabel()
        self.labelAskVolume1 = QtWidgets.QLabel()
        self.labelAskVolume2 = QtWidgets.QLabel()
        self.labelAskVolume3 = QtWidgets.QLabel()
        self.labelAskVolume4 = QtWidgets.QLabel()
        self.labelAskVolume5 = QtWidgets.QLabel()

        labelLast = QtWidgets.QLabel(u'最新')
        self.labelLastPrice = QtWidgets.QLabel()
        self.labelReturn = QtWidgets.QLabel()

        self.labelLastPrice.setMinimumWidth(60)
        self.labelReturn.setMinimumWidth(60)

        gridRight = QtWidgets.QGridLayout()
        gridRight.addWidget(labelAsk5, 0, 0)
        gridRight.addWidget(labelAsk4, 1, 0)
        gridRight.addWidget(labelAsk3, 2, 0)
        gridRight.addWidget(labelAsk2, 3, 0)
        gridRight.addWidget(labelAsk1, 4, 0)
        gridRight.addWidget(labelLast, 5, 0)
        gridRight.addWidget(labelBid1, 6, 0)
        gridRight.addWidget(labelBid2, 7, 0)
        gridRight.addWidget(labelBid3, 8, 0)
        gridRight.addWidget(labelBid4, 9, 0)
        gridRight.addWidget(labelBid5, 10, 0)

        gridRight.addWidget(self.labelAskPrice5, 0, 1)
        gridRight.addWidget(self.labelAskPrice4, 1, 1)
        gridRight.addWidget(self.labelAskPrice3, 2, 1)
        gridRight.addWidget(self.labelAskPrice2, 3, 1)
        gridRight.addWidget(self.labelAskPrice1, 4, 1)
        gridRight.addWidget(self.labelLastPrice, 5, 1)
        gridRight.addWidget(self.labelBidPrice1, 6, 1)
        gridRight.addWidget(self.labelBidPrice2, 7, 1)
        gridRight.addWidget(self.labelBidPrice3, 8, 1)
        gridRight.addWidget(self.labelBidPrice4, 9, 1)
        gridRight.addWidget(self.labelBidPrice5, 10, 1)

        gridRight.addWidget(self.labelAskVolume5, 0, 2)
        gridRight.addWidget(self.labelAskVolume4, 1, 2)
        gridRight.addWidget(self.labelAskVolume3, 2, 2)
        gridRight.addWidget(self.labelAskVolume2, 3, 2)
        gridRight.addWidget(self.labelAskVolume1, 4, 2)
        gridRight.addWidget(self.labelReturn, 5, 2)
        gridRight.addWidget(self.labelBidVolume1, 6, 2)
        gridRight.addWidget(self.labelBidVolume2, 7, 2)
        gridRight.addWidget(self.labelBidVolume3, 8, 2)
        gridRight.addWidget(self.labelBidVolume4, 9, 2)
        gridRight.addWidget(self.labelBidVolume5, 10, 2)

        # 发单按钮
        buttonSendOrder = QtWidgets.QPushButton(u'发单')
        buttonCancelAll = QtWidgets.QPushButton(u'全撤')

        size = buttonSendOrder.sizeHint()
        buttonSendOrder.setMinimumHeight(size.height() * 2)  # 把按钮高度设为默认两倍
        buttonCancelAll.setMinimumHeight(size.height() * 2)

        # 整合布局
        hbox = QtWidgets.QHBoxLayout()
        hbox.addLayout(gridleft)
        hbox.addLayout(gridRight)

        vbox = QtWidgets.QVBoxLayout()
        vbox.addLayout(hbox)
        vbox.addWidget(buttonSendOrder)
        vbox.addWidget(buttonCancelAll)
        vbox.addStretch()

        self.setLayout(vbox)

        # 关联更新
        buttonSendOrder.clicked.connect(self.sendOrder)
        buttonCancelAll.clicked.connect(self.cancelAll)
        self.lineSymbol.returnPressed.connect(self.updateSymbol)

    def updateSymbol(self):
        """合约变化"""
        # 读取组件数据
        symbol = str(self.lineSymbol.text())
        exchange = self.comboExchange.currentText()
        currency = self.comboCurrency.currentText()
        productClass = self.comboProductClass.currentText()
        gatewayName = self.comboGateway.currentText()

        # 查询合约
        if exchange:
            vtSymbol = '.'.join([symbol, exchange])
            contract = self.mainEngine.getContract(vtSymbol)
        else:
            vtSymbol = symbol
            contract = self.mainEngine.getContract(symbol)

        if contract:
            vtSymbol = contract.vtSymbol
            gatewayName = contract.gatewayName
            self.lineName.setText(contract.name)
            exchange = contract.exchange  # 保证有交易所代码

        # 清空价格数量
        self.spinPrice.setValue(0)
        self.spinVolume.setValue(0)

        # 清空行情显示
        self.labelBidPrice1.setText('')
        self.labelBidPrice2.setText('')
        self.labelBidPrice3.setText('')
        self.labelBidPrice4.setText('')
        self.labelBidPrice5.setText('')
        self.labelBidVolume1.setText('')
        self.labelBidVolume2.setText('')
        self.labelBidVolume3.setText('')
        self.labelBidVolume4.setText('')
        self.labelBidVolume5.setText('')
        self.labelAskPrice1.setText('')
        self.labelAskPrice2.setText('')
        self.labelAskPrice3.setText('')
        self.labelAskPrice4.setText('')
        self.labelAskPrice5.setText('')
        self.labelAskVolume1.setText('')
        self.labelAskVolume2.setText('')
        self.labelAskVolume3.setText('')
        self.labelAskVolume4.setText('')
        self.labelAskVolume5.setText('')
        self.labelLastPrice.setText('')
        self.labelReturn.setText('')

        # 重新注册事件监听
        if self.quoteEventEngine:
            self.quoteEventEngine.unregister(EVENT_TICK + self.symbol, self.signal.emit)
            self.quoteEventEngine.register(EVENT_TICK + vtSymbol, self.signal.emit)

        # 订阅合约
        req = SubscribeReq()
        req.symbol = symbol
        req.exchange = exchange
        req.currency = currency
        req.productClass = productClass
        # todo
        # self.mainEngine.subscribe(req, gatewayName)

        # 更新组件当前交易的合约
        self.symbol = vtSymbol

    def updateTick(self, event):
        """更新行情"""
        tick = event.dict_['data']

        if tick.vtSymbol == self.symbol:
            self.labelBidPrice1.setText(str(tick.bidPrice1))
            self.labelAskPrice1.setText(str(tick.askPrice1))
            self.labelBidVolume1.setText(str(tick.bidVolume1))
            self.labelAskVolume1.setText(str(tick.askVolume1))

            if tick.bidPrice2:
                self.labelBidPrice2.setText(str(tick.bidPrice2))
                self.labelBidPrice3.setText(str(tick.bidPrice3))
                self.labelBidPrice4.setText(str(tick.bidPrice4))
                self.labelBidPrice5.setText(str(tick.bidPrice5))

                self.labelAskPrice2.setText(str(tick.askPrice2))
                self.labelAskPrice3.setText(str(tick.askPrice3))
                self.labelAskPrice4.setText(str(tick.askPrice4))
                self.labelAskPrice5.setText(str(tick.askPrice5))

                self.labelBidVolume2.setText(str(tick.bidVolume2))
                self.labelBidVolume3.setText(str(tick.bidVolume3))
                self.labelBidVolume4.setText(str(tick.bidVolume4))
                self.labelBidVolume5.setText(str(tick.bidVolume5))

                self.labelAskVolume2.setText(str(tick.askVolume2))
                self.labelAskVolume3.setText(str(tick.askVolume3))
                self.labelAskVolume4.setText(str(tick.askVolume4))
                self.labelAskVolume5.setText(str(tick.askVolume5))

            self.labelLastPrice.setText(str(tick.lastPrice))

            if tick.preClosePrice:
                rt = (tick.lastPrice / tick.preClosePrice) - 1
                self.labelReturn.setText(('%.2f' % (rt * 100)) + '%')
            else:
                self.labelReturn.setText('')

    def connectSignal(self):
        """连接Signal"""
        self.signal.connect(self.updateTick)

    def sendOrder(self):
        """发单"""
        symbol = str(self.lineSymbol.text())
        exchange = self.comboExchange.currentText()
        currency = self.comboCurrency.currentText()
        productClass = self.comboProductClass.currentText()
        gatewayName = self.comboGateway.currentText()

        # 查询合约
        if exchange:
            vtSymbol = '.'.join([symbol, exchange])
            contract = self.mainEngine.getContract(vtSymbol)
        else:
            vtSymbol = symbol
            contract = self.mainEngine.getContract(symbol)

        if contract:
            gatewayName = contract.gatewayName
            exchange = contract.exchange  # 保证有交易所代码

        req = OrderReq()
        req.symbol = symbol
        req.exchange = exchange
        req.price = self.spinPrice.value()
        req.volume = self.spinVolume.value()
        req.direction = self.comboDirection.currentText()
        req.priceType = self.comboPriceType.currentText()
        req.offset = self.comboOffset.currentText()
        req.currency = currency
        req.productClass = productClass

        # print((req.direction,req.offset))
        self.mainEngine.send_order(req, gatewayName)

        # todo

    def cancelAll(self):
        """一键撤销所有委托"""
        l = self.mainEngine.get_working_orders()
        for order in l:
            req = CancelOrderReq()
            req.symbol = order.symbol
            req.exchange = order.exchange
            req.frontID = order.frontID
            req.sessionID = order.sessionID
            req.orderID = order.orderID
            self.mainEngine.delete_order(req, order.gatewayName)

    def closePosition(self, cell):
        """根据持仓信息自动填写交易组件"""
        # 读取持仓数据，cell是一个表格中的单元格对象
        pos = cell.data
        symbol = pos.symbol

        # 更新交易组件的显示合约
        self.lineSymbol.setText(symbol)
        self.updateSymbol()

        # 自动填写信息
        self.comboPriceType.setCurrentIndex(self.priceTypeList.index(PRICETYPE_LIMITPRICE))
        self.comboOffset.setCurrentIndex(self.offsetList.index(OFFSET_CLOSE))
        self.spinVolume.setValue(pos.position)

        if pos.direction == DIRECTION_LONG or pos.direction == DIRECTION_NET:
            self.comboDirection.setCurrentIndex(self.directionList.index(DIRECTION_SHORT))
        else:
            self.comboDirection.setCurrentIndex(self.directionList.index(DIRECTION_LONG))

        # 价格留待更新后由用户输入，防止有误操作


########################################################################
class ContractMonitor(BasicMonitor):
    """合约查询"""

    def __init__(self, mainEngine=None, eventEngine=None, parent=None):
        # print("""Constructor""")
        super(ContractMonitor, self).__init__(parent=parent)

        self.mainEngine = mainEngine
        self.eventEngine = eventEngine

        d = OrderedDict()
        d['symbol'] = {'chinese': u'合约代码', 'cellType': BasicCell}
        d['exchange'] = {'chinese': u'交易所', 'cellType': BasicCell}
        # d['vtSymbol'] = {'chinese': u'vt系统代码', 'cellType': BasicCell}
        d['name'] = {'chinese': u'名称', 'cellType': BasicCell}
        d['productClass'] = {'chinese': u'合约类型', 'cellType': BasicCell}
        d['size'] = {'chinese': u'乘数', 'cellType': BasicCell}
        d['priceTick'] = {'chinese': u'最小价格变动', 'cellType': BasicCell}
        d['OpenRatioByMoney'] = {'chinese': '开仓费率', 'cellType': BasicCell}
        d['OpenRatioByVolume'] = {'chinese': '开仓手续费', 'cellType': BasicCell}
        d['CloseRatioByMoney'] = {'chinese': '平仓费率', 'cellType': BasicCell}
        d['CloseRatioByVolume'] = {'chinese': '平仓手续费', 'cellType': BasicCell}
        d['CloseTodayRatioByMoney'] = {'chinese': '平今费率', 'cellType': BasicCell}
        d['CloseTodayRatioByVolume'] = {'chinese': '平今手续费', 'cellType': BasicCell}

        d['LongMarginRatioByMoney'] = {'chinese': '多头保证金率', 'cellType': BasicCell}
        # d['LongMarginRatioByVolume'] = {'chinese': '多头保证金', 'cellType': BasicCell}
        d['ShortMarginRatioByMoney'] = {'chinese': '空头保证金率', 'cellType': BasicCell}
        # d['ShortMarginRatioByVolume'] = {'chinese': '空头保证金', 'cellType': BasicCell}
        # d['strikePrice'] = {'chinese':u'期权行权价', 'cellType':BasicCell}
        # d['underlyingSymbol'] = {'chinese':u'期权标的物', 'cellType':BasicCell}
        # d['optionType'] = {'chinese':u'期权类型', 'cellType':BasicCell}
        self.setHeaderDict(d)

        self.initUi()

    def initUi(self):
        # print("""初始化界面""")
        self.setWindowTitle(u'合约查询')
        self.setMinimumSize(80, 80)
        self.setFont(BASIC_FONT)
        self.initTable()
        self.addMenuAction()

    def showAllContracts(self):
        """显示所有合约数据"""
        l = self.mainEngine.getAllContracts()
        # print(l.__dict__)

        d = {'.'.join([contract['exchange'], contract['symbol']]): contract for contract in l}

        l2 = d.keys()
        print(d, l2)
        # l2.sort(reverse=True)

        self.setRowCount(len(l2))
        row = 0

        for key in l2:
            contract = d[key]
            # print(contract.__dict__)
            if contract['productClass'] == '组合' or contract['productClass'] == '未知':
                continue
            for n, header in enumerate(self.headerList):
                content = safe_unicode(contract[header])
                cellType = self.headerDict[header]['cellType']
                cell = cellType(content)

                if self.font:
                    cell.setFont(self.font)  # 如果设置了特殊字体，则进行单元格设置

                self.setItem(row, n, cell)

            row = row + 1

    def refresh(self):
        """刷新"""
        self.menu.close()  # 关闭菜单
        self.clearContents()
        self.setRowCount(0)
        self.showAllContracts()

    def addMenuAction(self):
        """增加右键菜单内容"""
        refreshAction = QtWidgets.QAction(u'刷新', self)
        refreshAction.triggered.connect(self.refresh)

        self.menu.addAction(refreshAction)

    def show(self):
        """显示"""
        super(ContractMonitor, self).show()
        self.refresh()


if __name__ == "__main__":
    pass
